Skip to content

Conversation

@mkleczek
Copy link
Contributor

Fixes #4134

  • Update documentation
  • Update changelog

@mkleczek mkleczek marked this pull request as draft October 22, 2025 04:34
@mkleczek mkleczek changed the title Reject non-empty aud when jet-aud is not configured Reject non-empty aud when jwt-aud is not configured Oct 22, 2025
@mkleczek mkleczek force-pushed the reject-aud-by-default branch from 41036fc to 720c893 Compare October 22, 2025 18:01
@steve-chavez
Copy link
Member

steve-chavez commented Oct 23, 2025

The simplest solution I can think of is to make jwt-aud a regular expression with \b\B (ie. reject anything) as a default value. It can be set explicitly to .* to match anything and also to aud1|aud2|aud3 to address #2099

So with this change we would break if JWTs with an aud come with the default jwt-aud configuration (empty).

To return to previous behavior (accept any aud), users would have to explicitly set jwt-aud = '.* (accept any).

Some observations:

  • If v13 is set to jwt-aud = '.*', this will fail?
  • We would need a clear error message plus a new error code PGRSTxxx for the case where an empty jwt-aud fails with a JWT with an aud
    • This way the user can search the PGRSTxxx code on the docs, where we can say "to return to previous behavior use .*".

My reasoning is: we should stop thinking about "protecting the system" and start thinking about "protecting user data processed by the system". In this context, the threat aud claim is supposed to protect against, is that an attacker steals a JWT and tries to use it to access user's data in unrelated context. Empty jwt-aud right now is hence a security hole. We should be secure by default so misconfiguration (ie. missing jwt-aud) should not lead to security breach. If an administrator wants to accept any aud claim then it should be configured explicitly.
#4134 (comment)

I think the above makes sense, we would need to mention it on the CHANGELOG.

@mkleczek mkleczek closed this Oct 28, 2025
@mkleczek mkleczek deleted the reject-aud-by-default branch October 28, 2025 10:40
@steve-chavez
Copy link
Member

@mkleczek Why was this closed? 🤔

@mkleczek mkleczek restored the reject-aud-by-default branch October 28, 2025 15:59
@mkleczek mkleczek reopened this Oct 28, 2025
@mkleczek
Copy link
Contributor Author

@mkleczek Why was this closed? 🤔

Looks like I’ve deleted the source branch by mistake and the PR was automatically closed.

@mkleczek mkleczek force-pushed the reject-aud-by-default branch 4 times, most recently from b7ebb24 to 6ad23b3 Compare October 30, 2025 09:21
… decisions

JWT cache implementation introduced two new modules: Auth.Jwt and Auth.JwtCache.
This refactoring reorganizes code in Auth and the above two modules so that reponsibilities and dependencies are more clear:

* parseClaims function was moved from Auth.Jwt back to Auth.
Thanks to it Auth.Jwt module became independent from AuthResult data structure and role handling. Its only purpose right now is to parse/verify tokens and validate claims
* validateClaims function in Auth.Jwt module was split to separate validateAud and validateTimeClaims functions. This change was necessary to allow Auth.JwtCache module to be the only place to decide what validations are cached.
* Introduced type level tagging of claim validation results so that it is possible to statically ensure all required validations were performed (see Auth.JwtCache.parseAndValidateClaims signature)
* Made Auth.Jwt module independent from Config module: validateAud no longer takes Config as an argument but a (Text -> Bool) function to validate audience values
*  Auth.JwtCache module was changed so that it is now possilble to cache claims validation results. Tagged claim validation result types are used to ensure all validations are performed regardless of the decision about what should be cached.
* JwtCache datatype in Auth.JwtCache module was renamed to CacheState with JwksNotConfigured, NotCaching and Caching constructors.
* Creation of a Sieve cache instance was moved to a CacheVariant typeclass function newCache
* NeedsReconfiguration typeclass was introduced to handle differences between different CacheVariants in deciding when cache reset is needed (if aud claim validation results are cached we need to reset cache when jwt-aud changes)
This change adds flexibility to aud claim validation. jwt-aud configuration property can now be specified as a regular expression. For example, it is now possible to
* configure multiple acceptable aud values with '|' regex operator, eg: 'audience1|audience2|audience3'
* accept any audience from a particular domain, eg: 'https://[a-z0-9]*\.example\.com'
@mkleczek mkleczek force-pushed the reject-aud-by-default branch from 6ad23b3 to 3de3de9 Compare October 30, 2025 09:24
Fixes PostgREST#4134 (JWT with aud claim should be rejected if jwt-aud is not set)

Updated default jwt-aud value in Config module.
Updated spec tests.
@mkleczek mkleczek force-pushed the reject-aud-by-default branch from 3de3de9 to e15e264 Compare October 30, 2025 09:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

JWT with aud claim should be rejected if jwt-aud is not set

2 participants